﻿/*----------------------------------------------------------------
    Copyright (C) 2021 Senparc
    
    文件名：OAuth2Controller.cs
    文件功能描述：提供OAuth2.0授权测试（关注微信公众号：盛派网络小助手，点击菜单【功能体验】 【OAuth2.0授权测试】即可体验）
    
    
    创建标识：Senparc - 20150312
----------------------------------------------------------------*/

//DPBMARK_FILE MP
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Senparc.Weixin.Exceptions;
using Senparc.CO2NET.Extensions;
using Senparc.Weixin.HttpUtility;
using Senparc.Weixin.MP.AdvancedAPIs;
using Senparc.Weixin.MP.AdvancedAPIs.OAuth;
using Senparc.Weixin.MP.CommonAPIs;
using System.Text;
using Senparc.Weixin.MP;
using Senparc.Weixin;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;

namespace Web.Controllers
{
    /// <summary>
    /// 微信授权
    /// </summary>
    public class OAuth2Controller : Controller
    {
        /// <summary>
        /// appId
        /// </summary>
        private readonly string appId = Config.SenparcWeixinSetting.WeixinAppId;//与微信公众账号后台的AppId设置保持一致，区分大小写。
        /// <summary>
        /// appSecret
        /// </summary>
        private readonly string appSecret = Config.SenparcWeixinSetting.WeixinAppSecret;//与微信公众账号后台的AppId设置保持一致，区分大小写。
        /// <summary>
        /// 域名
        /// </summary>
        string DomainUrl = CoreHelper.AppSettingsManager.Get("basesetting:DomainUrl");

        #region OAuthScope.snsapi_userinfo方式授权
        /// <summary>
        /// OAuthScope.snsapi_userinfo方式授权
        /// </summary>
        /// <param name="returnUrl">用户尝试进入的需要登录的页面</param>
        /// <returns></returns>
        public ActionResult Index(string returnUrl)
        {
            var state = "JeffreySu-" + SystemTime.Now.Millisecond;//随机数，用于识别请求可靠性
            HttpContext.Session.SetString("State", state);//储存随机数到Session
            string UrlUserInfo = OAuthApi.GetAuthorizeUrl(appId, DomainUrl + "/oauth2/UserInfoCallback?returnUrl=" + returnUrl.UrlEncode(), state, OAuthScope.snsapi_userinfo);
            return Redirect(UrlUserInfo);
        }

        /// <summary>
        /// OAuthScope.snsapi_userinfo方式回调
        /// </summary>
        /// <param name="code"></param>
        /// <param name="state"></param>
        /// <param name="returnUrl">用户最初尝试进入的页面</param>
        /// <returns></returns>
        public ActionResult UserInfoCallback(string code, string state, string returnUrl)
        {
            if (string.IsNullOrEmpty(code))
            {
                return Content("您拒绝了授权！");
            }

            //if (state != HttpContext.Session.GetString("State"))
            //{
            //    //这里的state其实是会暴露给客户端的，验证能力很弱，这里只是演示一下，
            //    //建议用完之后就清空，将其一次性使用
            //    //实际上可以存任何想传递的数据，比如用户ID，并且需要结合例如下面的Session["OAuthAccessToken"]进行验证
            //    return Content("验证失败！请从正规途径进入！");
            //}

            OAuthAccessTokenResult result = null;

            //通过，用code换取access_token
            try
            {
                result = OAuthApi.GetAccessToken(appId, appSecret, code);
            }
            catch (Exception ex)
            {
                return Content(ex.Message);
            }
            if (result.errcode != ReturnCode.请求成功)
            {
                return Content("错误：" + result.errmsg);
            }
            //下面2个数据也可以自己封装成一个类，储存在数据库中（建议结合缓存）
            //如果可以确保安全，可以将access_token存入用户的cookie中，每一个人的access_token是不一样的
            HttpContext.Session.SetString("OAuthAccessTokenStartTime", SystemTime.Now.ToString());
            HttpContext.Session.SetString("OAuthAccessToken", result.ToJson());

            #region 记录登录信息
            //创建一个身份认证
            var identity = new ClaimsIdentity("Identity.Application");
            identity.AddClaim(new Claim(ClaimTypes.Name, result.openid));//用户openid
            ClaimsPrincipal principal = new ClaimsPrincipal(identity);

            #region 有效期
            var authenticationProperties = new AuthenticationProperties
            {
                IsPersistent = false,//持久
                ExpiresUtc = DateTime.Now.AddMinutes(300),//过期时间，在当前时间基础上加XX分钟
                AllowRefresh = false
            };
            #endregion

            HttpContext.SignInAsync(
               "Identity.Application"//这里要注意的是HttpContext.SignInAsync(AuthenticationType,…) 所设置的Scheme一定要与前面的配置一样，这样对应的登录授权才会生效。
               , principal
               , authenticationProperties//有效期
               );

            //string openid = HttpContext.User.Identity.Name;//控制器中获取openid调用方法
            #endregion            

            //返回授权前的链接
            return Redirect(returnUrl);
        }
        #endregion

        #region OAuthScope.snsapi_base方式授权
        /// <summary>
        /// OAuthScope.snsapi_base方式授权
        /// </summary>
        /// <param name="returnUrl">授权前Url</param>
        /// <returns></returns>
        public ActionResult indexBase(string returnUrl)
        {
            var state = "JeffreySu-" + DateTime.Now.Millisecond;//随机数，用于识别请求可靠性
            HttpContext.Session.SetString("State", state);//储存随机数到Session
            string UrlBase = OAuthApi.GetAuthorizeUrl(appId, DomainUrl + "/oauth2/BaseCallback?returnUrl=" + returnUrl.UrlEncode(), state, OAuthScope.snsapi_base);
            return Redirect(UrlBase);
        }

        /// <summary>
        /// OAuthScope.snsapi_base方式回调
        /// </summary>
        /// <param name="code"></param>
        /// <param name="state"></param>
        /// <param name="returnUrl">用户最初尝试进入的页面</param>
        /// <returns></returns>
        public ActionResult BaseCallback(string code, string state, string returnUrl)
        {
            try
            {
                if (string.IsNullOrEmpty(code))
                {
                    return Content("您拒绝了授权！");
                }

                //if (state != HttpContext.Session.GetString("State"))
                //{
                //    //这里的state其实是会暴露给客户端的，验证能力很弱，这里只是演示一下，
                //    //建议用完之后就清空，将其一次性使用
                //    //实际上可以存任何想传递的数据，比如用户ID，并且需要结合例如下面的Session["OAuthAccessToken"]进行验证
                //    return Content("验证失败！请从正规途径进入！");
                //}

                //通过，用code换取access_token
                var result = OAuthApi.GetAccessToken(appId, appSecret, code);
                if (result.errcode != ReturnCode.请求成功)
                {
                    LogHelper.Error(string.Format("微信snsapi_base授权,用code换取access_token:{0}", result.errcode));
                    return Content("错误：" + result.errmsg);
                }

                //下面2个数据也可以自己封装成一个类，储存在数据库中（建议结合缓存）
                //如果可以确保安全，可以将access_token存入用户的cookie中，每一个人的access_token是不一样的
                HttpContext.Session.SetString("OAuthAccessTokenStartTime", SystemTime.Now.ToString());
                HttpContext.Session.SetString("OAuthAccessToken", result.ToJson());

                LogHelper.Info($"微信snsapi_base授权{result.openid}", LogHelper.logsSource.微信, result.openid);

                #region 记录登录信息
                //创建一个身份认证
                var identity = new ClaimsIdentity("Identity.Application");
                identity.AddClaim(new Claim(ClaimTypes.Name, result.openid));//用户openid
                ClaimsPrincipal principal = new ClaimsPrincipal(identity);

                #region 有效期
                var authenticationProperties = new AuthenticationProperties
                {
                    IsPersistent = false,//持久
                    ExpiresUtc = DateTime.Now.AddMinutes(300),//过期时间，在当前时间基础上加XX分钟
                    AllowRefresh = false
                };
                #endregion

                HttpContext.SignInAsync(
					"Identity.Application"//这里要注意的是HttpContext.SignInAsync(AuthenticationType,…) 所设置的Scheme一定要与前面的配置一样，这样对应的登录授权才会生效。
				   , principal
                   , authenticationProperties//有效期
                   );
                #endregion

                #region 调用获取openid
                //string openid = HttpContext.User.Identity.Name;//openid
                #endregion

                return Redirect(returnUrl);
            }
            catch (Exception ex)
            {
                LogHelper.Error($"snsapi_base授权异常", LogHelper.logsSource.微信, "snsapi_base授权", ex);
                return Content("发生错误："+ex.ToString());
            }
        }
        #endregion
    }
}